library(Seurat)
library(SnapATAC)
library(tidyverse)
library(DescTools)  # 4 AUC function
library(glue)
library(ggalluvial)  # 4 river plot
library(ggpubr)
source("~/multiOmic_benchmark/utils.R")
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}
## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191106_labelTransferEDA_F74/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE
integrate_features <- scan("~/models/intFeatures_union_hvg_2000_F74_SCElist_20191101.txt", what='')
Read 3027 items

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

## Load original data
orig.ATAC <- readRDS("~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")
sce.list <- readRDS("~/my_data/integrated_thymus/F74_SCElist_20191101.RDS")
orig.RNA <- sce.list$RNA
## Make SeuratObjects
atac.seu <- snapToSeurat(
    obj=orig.ATAC, 
    eigs.dims=1:20, 
    norm=TRUE,
    scale=TRUE
    )
Epoch: checking input parameters ... 
Non-unique features (rownames) present in the input matrix, making uniquePerforming log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Centering and scaling data matrix

  |                                                                                       
  |                                                                                 |   0%
  |                                                                                       
  |==                                                                               |   3%
  |                                                                                       
  |=====                                                                            |   6%
  |                                                                                       
  |=======                                                                          |   9%
  |                                                                                       
  |==========                                                                       |  12%
  |                                                                                       
  |============                                                                     |  15%
  |                                                                                       
  |==============                                                                   |  18%
  |                                                                                       
  |=================                                                                |  21%
  |                                                                                       
  |===================                                                              |  24%
  |                                                                                       
  |=====================                                                            |  26%
  |                                                                                       
  |========================                                                         |  29%
  |                                                                                       
  |==========================                                                       |  32%
  |                                                                                       
  |=============================                                                    |  35%
  |                                                                                       
  |===============================                                                  |  38%
  |                                                                                       
  |=================================                                                |  41%
  |                                                                                       
  |====================================                                             |  44%
  |                                                                                       
  |======================================                                           |  47%
  |                                                                                       
  |========================================                                         |  50%
  |                                                                                       
  |===========================================                                      |  53%
  |                                                                                       
  |=============================================                                    |  56%
  |                                                                                       
  |================================================                                 |  59%
  |                                                                                       
  |==================================================                               |  62%
  |                                                                                       
  |====================================================                             |  65%
  |                                                                                       
  |=======================================================                          |  68%
  |                                                                                       
  |=========================================================                        |  71%
  |                                                                                       
  |============================================================                     |  74%
  |                                                                                       
  |==============================================================                   |  76%
  |                                                                                       
  |================================================================                 |  79%
  |                                                                                       
  |===================================================================              |  82%
  |                                                                                       
  |=====================================================================            |  85%
  |                                                                                       
  |=======================================================================          |  88%
  |                                                                                       
  |==========================================================================       |  91%
  |                                                                                       
  |============================================================================     |  94%
  |                                                                                       
  |===============================================================================  |  97%
  |                                                                                       
  |=================================================================================| 100%
atac.seu <- RenameCells(atac.seu, new.names = orig.ATAC@metaData$barcode)
## Add cell type predictions
getPredictedLabels <- function(seu.int, int.name, id.col="predicted.id", score.col="score"){
  pred.df <- seu.int$ATAC@meta.data[,c(id.col, score.col), drop=F] 
  rownames(pred.df) <- str_remove(rownames(pred.df), "^ATAC_")
  colnames(pred.df) <- c(str_c("predicted.id", "_", int.name), str_c("score", "_", int.name))
  pred.df
  }
pred.cca <- getPredictedLabels(seu.cca, "CCA", score.col = "prediction.score.max")
pred.liger <- getPredictedLabels(seu.liger, "Liger")
pred.conos <- getPredictedLabels(seu.conos, "Conos")
if (all(rownames(pred.conos) == rownames(pred.cca)) & all(rownames(pred.conos) == rownames(pred.liger))) {
  atac.seu <- AddMetaData(atac.seu, metadata = cbind(pred.cca, pred.liger, pred.conos))
} else {
  stop("Non corresponding cell names")
}

pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Liger", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Liger")
plotly::ggplotly(pl)
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix

  |                                                                                                                                              
  |                                                                                                                                        |   0%
  |                                                                                                                                              
  |====================================================================                                                                    |  50%
  |                                                                                                                                              
  |========================================================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1 
Positive:  TRBC2, TRBC1, HMGA1, HIST1H1C, HIST1H3H, ITM2A, HIST1H2BJ, SMIM24, TRAV13-2, FXYD2 
       TRBV7-2, PCGF5, HIST1H2BH, IL32, HIST1H2BN, CHAC1, RASD1, TRBV27, TRAV13-1, TRAV8-2 
       TRAV8-4, PTPN6, SELL, HIST1H2BG, TAGAP, TRDC, TRAV38-2DV8, TRAV29DV5, TRBV9, TRAV41 
Negative:  CALD1, COL5A2, COL6A1, COL6A2, SPARC, THY1, DCN, COL3A1, NFIB, SPARCL1 
       TSHZ2, CPE, PLAC9, NID1, FKBP10, PTN, FLRT2, MAP1B, EFEMP2, BGN 
       CXCL12, RBP1, LAMB1, AHNAK, COL1A1, COL5A1, FSTL1, LUM, LAMA4, MDK 
PC_ 2 
Positive:  SFRP1, NTRK2, PLAT, ISLR, NRK, SCARA5, ASPN, OSR1, OLFML3, MXRA8 
       CAPN6, PTPRD, PLP1, TMEFF2, CREB3L1, DKK3, CERCAM, MMP2, EBF2, SMOC2 
       CDO1, COL12A1, PDGFRA, LRRC17, THBS2, HTRA3, SFRP2, ANGPTL1, MAB21L1, MXRA5 
Negative:  MKI67, CDK1, NUSAP1, TOP2A, CCNA2, RRM2, UBE2C, BIRC5, KIFC1, TYMS 
       UBE2T, AURKB, CENPF, CENPM, CDCA8, TACC3, NCAPG, TPX2, ASF1B, CDKN3 
       GTSE1, CDCA3, HJURP, SPC25, MAD2L1, CDC20, PLK1, DLGAP5, NUF2, KIF22 
PC_ 3 
Positive:  CCNA2, NUF2, GTSE1, CDCA8, UBE2T, AURKB, PBK, CDK1, NCAPG, NDC80 
       KIFC1, CDCA3, HJURP, MAD2L1, SPC25, PLK1, KIF15, DEPDC1B, BIRC5, CDCA5 
       CKS1B, RRM2, DLGAP5, HMMR, CENPA, KIF22, KIF20A, CDCA2, CENPF, KIF2C 
Negative:  HLA-DRB5, HLA-DRA, TYROBP, HLA-DRB1, HLA-DPA1, HLA-DPB1, HLA-DQA1, C1QC, C1QB, RNASE1 
       HLA-DQB1, A2M, C1QA, CSF1R, STAB1, HCK, HLA-DMB, FOLR2, SAMHD1, LYZ 
       MS4A7, SPI1, CD36, MS4A6A, CYBB, TMEM176B, CD74, IGSF6, MPEG1, MS4A4A 
PC_ 4 
Positive:  GYPA, GYPB, NFE2, GYPE, ANK1, SLC4A1, RHAG, AHSP, DMTN, KLF1 
       GATA1, TMOD1, TMEM56, HBZ, HBG1, GMPR, C17orf99, SMIM5, HBQ1, TSPO2 
       ALAS2, PHOSPHO1, CR1L, TRIM58, HBM, EPB42, RHD, RHCE, SPTA1, SMIM1 
Negative:  TMSB10, TRBC2, CCL21, IL32, CXCL13, TRBC1, APLNR, CCL19, MIF, HMGB1 
       COX4I2, COL4A1, COL15A1, MADCAM1, FDCSP, CCL17, NTS, TNC, CDH5, FABP4 
       CAV1, COL4A2, CRIP2, RGS5, KLRB1, MYLK, IFITM1, IL33, PAPLN, HPN 
PC_ 5 
Positive:  APLNR, CDH5, CAV1, COL15A1, CRIP2, COL4A1, CCL21, KDR, CLDN5, MADCAM1 
       FABP4, IL33, COL4A2, CXCL13, TM4SF1, PODXL, CCL19, COX4I2, ESAM, BCAM 
       NTS, PAPLN, SPNS2, MYLK, C8orf4, ADGRF5, TM4SF18, TGM2, RP11-536O18.1, CXorf36 
Negative:  CSF1R, MS4A4A, CYBB, PLD4, MS4A6A, CD163, HCK, IGSF6, FOLR2, ADAP2 
       CD86, MS4A7, MARCH1, MRC1, F13A1, MPEG1, CD14, SPI1, HLA-DMB, GPR34 
       CD33, TGFBI, CLEC7A, TIMD4, CEBPA, SIGLEC1, CSF2RA, SLC15A3, LY86, AGR2 
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:30)
13:04:19 UMAP embedding parameters a = 0.9922 b = 1.112
13:04:19 Read 8321 rows and found 30 numeric columns
13:04:19 Using Annoy for neighbor search, n_neighbors = 30
13:04:19 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
13:04:21 Writing NN index file to temp file /tmp/Rtmpc3mqss/file9a4b15e3cd
13:04:21 Searching Annoy index using 1 thread, search_k = 3000
13:04:24 Annoy recall = 100%
13:04:27 Commencing smooth kNN distance calibration using 1 thread
13:04:30 Initializing from normalized Laplacian + noise
13:04:30 Commencing optimization for 500 epochs, with 360828 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
13:04:51 Optimization finished
plotly::ggplotly(DimPlot(orig.RNA.seu, group.by="annotation"))

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 22 rows containing non-finite values (stat_ecdf).

Cell type composition

Compare cell type fractions (w uncertainty)

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  bind_rows(orig.frac.df) %>%
  mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
  mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
  # select(method, predicted.id, frac.label) %>%
  # distinct() %>%
  ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
  geom_point() +
  geom_col(width=0.1) +
  coord_flip() +
  # geom_line(aes(group=method)) +
  facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
  scale_color_viridis_c() +
  scale_fill_viridis_c() +
  ylab("Fraction of cells") +
  theme_bw(base_size = 16) +
  ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 7)
binding character and factor vector, coercing into character vector

Does the uncertainty depend on the size of the cluster?

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=median(score), sd.score=mad(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  # bind_rows(orig.frac.df) %>%
  ggplot(aes(frac.label, mean.score, color=method)) +
  geom_point(size=2) +
  geom_errorbar(aes(ymin=mean.score-sd.score, ymax=mean.score+sd.score), alpha=0.6) +
  scale_color_brewer(palette="Set1") +
  # geom_smooth(method = "loess", span=1.2) +
  facet_grid(. ~ method) +
  theme_bw(base_size = 16) +
  stat_cor(label.x = 0.2, label.y=0.25, color="black", size=5) 

NA
NA

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

k = 50
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:15, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
score.CCA <- imap_dbl(atac.nn.list, ~ sum(pred.cca[.x,1] == pred.cca[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Conos <- imap_dbl(atac.nn.list, ~ sum(pred.conos[.x,1] == pred.conos[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Liger <- imap_dbl(atac.nn.list, ~ sum(pred.liger[.x,1] == pred.liger[.y,1])/k) %>% setNames(names(atac.nn.list))
knn_score_df <-
  as.data.frame(cbind(score.Conos, score.Liger, score.CCA)) %>%
  rownames_to_column("cell") %>%
  pivot_longer(cols=str_subset(colnames(.), "score"), names_to = "method", values_to = "KNN_score") %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score),
                method=str_remove(method, "score."))
quants = seq(0,1, by = 0.05)
AUECDF_knn_score <- knn_score_df %>%
  split(.$method) %>%
  map_dbl( ~ .x %>%
      arrange(KNN_score) %>% 
      {ecdf(.$KNN_score)(quants)} %>% AUC(quants,.)
    )
  
knn_score_df %>%
  mutate(AUC=AUECDF_knn_score[method]) %>%
  ggplot(aes(KNN_score, color=method, fill=method)) +
  stat_ecdf(size=1) +
  scale_color_brewer(palette = "Set1") +
  geom_text(data=. %>% group_by(method) %>% summarise(AUC=max(AUC)), 
            x=0.05, hjust=0,
            aes(label=glue("AUC = {round(AUC, 3)}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") +
  ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 4, width=6)

full_join(pred.labels.df, knn_score_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf() +
  facet_wrap("predicted.id") +
  scale_color_brewer(palette = "Set1") +
  coord_fixed()
Joining, by = c("cell", "method")

plot_KNNecdf <- function(cluster){
  full_join(pred.labels.df, knn_score_df) %>%
    filter(predicted.id==cluster) %>%
    ggplot(aes(KNN_score, color=method)) +
    stat_ecdf(size=0.8) +
    facet_wrap("predicted.id") +
    xlim(0,1) + ylim(0,1) +
    coord_fixed() +
    scale_color_brewer(palette = "Set1") +
    theme_bw(base_size = 16) +
    theme(legend.position = "top")
}

DimPlotCluster <- function(annotation_col, cluster, label){
  highlight = which(atac.seu@meta.data[,annotation_col]==cluster)
  DimPlot(atac.seu, reduction = "umap.snap",cells.highlight = highlight, cols.highlight = "red", pt.size = 0.02, sizes.highlight = 0.1) +
    guides(color="none") +
    ggtitle(label = label)
  }

UMAPs_cluster <- function(cluster){
  ggarrange(plotlist=imap(list(CCA="predicted.id_CCA", Conos="predicted.id_Conos", Liger="predicted.id_Liger"), ~ DimPlotCluster(.x, cluster, label = .y )), ncol=3, nrow=1) %>% annotate_figure(cluster)
}

map(cell.types, ~ ggarrange(plot_KNNecdf(.x), UMAPs_cluster(.x), nrow = 2, heights = c(1,0.8)))

Which cells are inconsistently aligned?

pred.labels.df %>%
  select(method, predicted.id, cell) %>%
  mutate(predicted.id=ifelse(is.na(predicted.id), "none", predicted.id)) %>%
  ggplot(aes(x=method, stratum=predicted.id, alluvium=cell, fill=predicted.id, label=predicted.id)) +
  geom_flow() +
  geom_stratum(color=NA) +
  geom_text(stat="stratum") +
  theme_bw(base_size = 16)

Which cells are inconsistently scored?

library(ggalluvial)
pred.labels.df %>%
  select(method, predicted.id, cell) %>%
  mutate(predicted.id=ifelse(is.na(predicted.id), "none", predicted.id)) %>%
  ggplot(aes(x=method, stratum=predicted.id, alluvium=cell, fill=predicted.id, label=predicted.id)) +
  geom_flow() +
  geom_stratum() +
  geom_text(stat="stratum") +
  theme_bw(base_size = 16)

Accessibility of markers

Taking markers from Fig. S2 of JP’s manuscript

thymus.markers <- c("PTPRC", "CD3G", "TYROBP","CD19","HOXA9",'FXYD2',"SH3TC1","CCR9","CD8A", "CD8B","PDCD1", "CRTAM","CD40LG","CCR6","FOXP3","SOX13","ZNF683","KLRD1","TNFSF11","VPREB1","MS4A1", "CLEC9A", "CLEC10A", "LAMP3", "IL3RA", "FCGR3B", "C2","TPSB2",
                    'ITGA2B',"GYPA", "CDH5", "RGS5","CDH1", "PDGFRA","CRABP1")
# pbmc.markers <- c("CD79A", "MS4A1", "CD8A", "CD8B", "LYZ")
# thymus.markers <- list(Fb=c("PDGFRA", "COLEC11", "FBN1", "PI16"),
#                        VSMC=c("PDGFRB", 'ACTA2', "RGS5"),
#                        Endo=c("PECAM1", "CDH5","LYVE1"),
#                        TEC = c("EPCAM", "FOXN1", "CCL25", "CCL19")
#                        )
thymus.markers.df <- imap(thymus.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
marker.access.df <- atac.seu@assays$ACTIVITY@data[intersect(thymus.markers, rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  # full_join(thymus.markers.df) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) 
ordered_cell_types <- c("DN(Q)", "DN(P)", "DP(Q)", "DP(P)", "αβT(entry)", "CD8+T", "CD4+T", "CD4+Tmem", 'Treg', "γδT", "NK", "ILC3", "DC1", "DC2", "aDC", "pDC", "Mac", "Ery", "Endo", "VSMC", "FB_1", "Fb_2")
markers_pl <- 
  marker.access.df %>%
  mutate(predicted.id = case_when(str_detect(predicted.id, "CD8") ~ "CD8+T",
                                  # str_detect(predicted.id, "CD4") ~ "CD4+T",
                                  TRUE ~ predicted.id
                                  )
         ) %>%
  mutate(predicted.id=factor(predicted.id, levels = ordered_cell_types)) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n()) %>%
  # filter(method=="CCA") %>%
  ungroup() %>%
  ggplot( aes( gene, predicted.id)) +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  facet_grid(method~., space="free", scales="free_x") +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_viridis_c() +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.x = element_text(angle=45)) 
markers_pl 

  
ggsave(paste0(outdir, "Thymus_markers_accessibility.png"), height = 16, width = 12)

Reproducing Fig.2H on T-cell development

tcells.markers.df %>%
  full_join(t.cell.markers.df) %>%
  # filter(method=="CCA") %>%
  mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
  ggplot(aes( predicted.id, gene)) +
  facet_grid(cell.type.class~method, scales = "free_y", space="free") +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_gradient2(midpoint = 0.5) +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.y = element_text(angle=0)) 
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

Compare feature selection strategy (reference based)

integrate_features_ref <- scan("~/models/intFeatures_reference_hvg_2000_F74_SCElist_20191101.txt", what = "")
Read 1893 items

score.CCA.ref <-   imap_dbl(atac.nn.list, ~ sum(pred.cca.ref[.x,1] == pred.cca.ref[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Conos.ref <- imap_dbl(atac.nn.list, ~ sum(pred.conos.ref[.x,1] == pred.conos.ref[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Liger.ref <- imap_dbl(atac.nn.list, ~ sum(pred.liger.ref[.x,1] == pred.liger.ref[.y,1])/k) %>% setNames(names(atac.nn.list))
knn_score_df <-
  as.data.frame(cbind(score.Conos.ref, score.Liger.ref, score.CCA.ref)) %>%
  rownames_to_column("cell") %>%
  pivot_longer(cols=str_subset(colnames(.), "score"), names_to = "method", values_to = "KNN_score") %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score),
                method=str_remove(method, "score."))
quants = seq(0,1, by = 0.05)
AUECDF_knn_score <- knn_score_df %>%
  split(.$method) %>%
  map_dbl( ~ .x %>%
      arrange(KNN_score) %>% 
      {ecdf(.$KNN_score)(quants)} %>% AUC(quants,.)
    )
  
knn_score_df %>%
  mutate(AUC=AUECDF_knn_score[method]) %>%
  ggplot(aes(KNN_score, color=method, fill=method)) +
  stat_ecdf(size=1) +
  scale_color_brewer(palette = "Set1") +
  geom_text(data=. %>% group_by(method) %>% summarise(AUC=max(AUC)), 
            x=0.05, hjust=0,
            aes(label=glue("AUC = {round(AUC, 3)}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") 

Is the union or the reference best maintaining the structure of the ATAC?


Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTEwNl9sYWJlbFRyYW5zZmVyRURBX0Y3NC8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KbW9kZWwuY2NhIDwtIHJlYWRSRFMoIn4vbW9kZWxzL21vZGVsQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQptb2RlbC5saWdlciA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbExpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQptb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L21vZGVscy9pbnRGZWF0dXJlc191bmlvbl9odmdfMjAwMF9GNzRfU0NFbGlzdF8yMDE5MTEwMS50eHQiLCB3aGF0PScnKQoKaW50Lmxpc3QgPC0gbGlzdChDQ0E9c2V1LmNjYSwgTGlnZXI9c2V1LmxpZ2VyLCBDb25vcz1zZXUuY29ub3MpCgojIyBNYWtlIG1ldGhvZCBjb2xvciBwYWxldHRlCm1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7cn0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToyMCwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKZ2V0UHJlZGljdGVkTGFiZWxzIDwtIGZ1bmN0aW9uKHNldS5pbnQsIGludC5uYW1lLCBpZC5jb2w9InByZWRpY3RlZC5pZCIsIHNjb3JlLmNvbD0ic2NvcmUiKXsKICBwcmVkLmRmIDwtIHNldS5pbnQkQVRBQ0BtZXRhLmRhdGFbLGMoaWQuY29sLCBzY29yZS5jb2wpLCBkcm9wPUZdIAogIHJvd25hbWVzKHByZWQuZGYpIDwtIHN0cl9yZW1vdmUocm93bmFtZXMocHJlZC5kZiksICJeQVRBQ18iKQogIGNvbG5hbWVzKHByZWQuZGYpIDwtIGMoc3RyX2MoInByZWRpY3RlZC5pZCIsICJfIiwgaW50Lm5hbWUpLCBzdHJfYygic2NvcmUiLCAiXyIsIGludC5uYW1lKSkKICBwcmVkLmRmCiAgfQoKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBzZXROYW1lcyhnZ19jb2xvcl9odWUobGVuZ3RoKGNlbGwudHlwZXMpKSwgY2VsbC50eXBlcykKCmF0YWMuc2V1IDwtIFJ1blVNQVAoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIHJlZHVjdGlvbi5uYW1lID0gInVtYXAuc25hcCIsIGRpbXM9MToyMCkKCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInVtYXBfbGFiZWxzLnBuZyIpLCB3aWR0aD0xNiwgaGVpZ2h0ID0gOCkKCgpgYGAKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9MaWdlciIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpCnBsb3RseTo6Z2dwbG90bHkocGwpCmBgYAoKCmBgYHtyfQpvcmlnLlJOQS5zZXUgPC0gYXMuU2V1cmF0KG9yaWcuUk5BKQpvcmlnLlJOQS5zZXUgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gU2NhbGVEYXRhKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blBDQShvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBSdW5VTUFQKG9yaWcuUk5BLnNldSwgZGltcz0xOjMwKQoKcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgZ3JvdXAuYnk9ImFubm90YXRpb24iKSkKYGBgCgojIyBQcmVkaWN0aW9uIHNjb3JlClF1YW50aWZpZXMgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBwcmVkaWN0aW9uLiBDYWxjdWxhdGVkIGRpZmZlcmVudGx5IGZvciBldmVyeSBtZXRob2QsIGJ1dCB1c2VkIHRvIGRlZmluZSB3aGljaCBjZWxscyBhcmUgInVuYXNzaWduZWQiLgoKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXycpCmxhYmVsX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAncHJlZGljdGVkLmlkXycpCgpwcmVkLmxhYmVscy5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLCBMaWdlcj1wcmVkLmxpZ2VyLCBDb25vcz1wcmVkLmNvbm9zKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZSksIDAsIHNjb3JlKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZSwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dwdWJyOjpnZ2FycmFuZ2UocHJlZGljdF9zY29yZV9oaXN0LCBwcmVkaWN0X3Njb3JlX2N1bWVkaXN0LCBjb21tb24ubGVnZW5kID0gVFJVRSwgd2lkdGhzID0gYygwLjgsIDEuMiksCiAgICAgICAgICBsYWJlbHM9YygiQSIsICJCIikpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfZGlzdHJpYnV0aW9uLnBuZyIpLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0NDQSIgICwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNDQSIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0xpZ2VyIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfQ29ub3MiLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAicHJlZGljdGlvbl9zY29yZV91bWFwcy5wbmciKSwgaGVpZ2h0ID0gNywgd2lkdGg9MTQpCmBgYAoKCiMjIENlbGwgdHlwZSBjb21wb3NpdGlvbgoKQ29tcGFyZSBjZWxsIHR5cGUgZnJhY3Rpb25zICh3IHVuY2VydGFpbnR5KQpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9Nn0KCnByZWQubGFiZWxzLmRmICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JQogIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lYW4oc2NvcmUpKSAlPiUKICBtdXRhdGUoZnJhYy5sYWJlbD10b3QubGFiZWwvdG90LmNlbGxzKSAlPiUKICAjIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JQogIGdncGxvdChhZXMobWV0aG9kLCBwcmVkaWN0ZWQuaWQpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9bWVhbi5zY29yZSwgc2l6ZT1mcmFjLmxhYmVsKSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhuYW1lPSJNZWFuIHByZWRpY3Rpb25cbnNjb3JlIikgKwogIHNjYWxlX3NoYXBlKG5hbWU9IkZyYWN0aW9uIG9mIGNlbGxzIikgKwogICMgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0iZ3JleSIsIGhpZ2g9InJlZCIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJjZWxsX3R5cGVfY29tcG9zaXRpb24ucG5nIiksIHdpZHRoPTYsIGhlaWdodCA9IDYpCiAgCgpgYGAKYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD03fQpvcmlnLnJhbmsuZGYgPC0gb3JpZy5mcmFjLmRmICU+JSAKICBtdXRhdGUob3JpZy5yYW5rPWRlbnNlX3JhbmsoZnJhYy5sYWJlbCkpICU+JQogIHNlbGVjdChvcmlnLnJhbmssIHByZWRpY3RlZC5pZCkgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBhcnJhbmdlKG9yaWcucmFuaykgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJwcmVkaWN0ZWQuaWQiKSAKCnByZWQubGFiZWxzLmRmICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JQogIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lYW4oc2NvcmUpKSAlPiUKICBtdXRhdGUoZnJhYy5sYWJlbD10b3QubGFiZWwvdG90LmNlbGxzKSAlPiUKICBiaW5kX3Jvd3Mob3JpZy5mcmFjLmRmKSAlPiUKICBtdXRhdGUob3JpZy5yYW5rID0gb3JpZy5yYW5rLmRmW3ByZWRpY3RlZC5pZCxdKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscz1yb3duYW1lcyhvcmlnLnJhbmsuZGYpKSklPiUKICAjIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgZnJhYy5sYWJlbCkgJT4lCiAgIyBkaXN0aW5jdCgpICU+JQogIGdncGxvdChhZXMocHJlZGljdGVkLmlkLCBmcmFjLmxhYmVsLCBmaWxsPW1lYW4uc2NvcmUsIGNvbG9yPW1lYW4uc2NvcmUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2NvbCh3aWR0aD0wLjEpICsKICBjb29yZF9mbGlwKCkgKwogICMgZ2VvbV9saW5lKGFlcyhncm91cD1tZXRob2QpKSArCiAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTQsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB5bGFiKCJGcmFjdGlvbiBvZiBjZWxscyIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiY2VsbF90eXBlX2NvbXBvc2l0aW9uX2JhcnMucG5nIiksIHdpZHRoID0gMTUsIGhlaWdodCA9IDcpCmBgYAoKRG9lcyB0aGUgdW5jZXJ0YWludHkgZGVwZW5kIG9uIHRoZSBzaXplIG9mIHRoZSBjbHVzdGVyPwpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTV9CgpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIGRyb3BfbmEoKSAlPiUKICBtdXRhdGUodG90LmNlbGxzPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUKICBzdW1tYXJpc2UodG90LmxhYmVsID0gbigpLCB0b3QuY2VsbHMgPSBtYXgodG90LmNlbGxzKSwgbWVhbi5zY29yZT1tZWRpYW4oc2NvcmUpLCBzZC5zY29yZT1tYWQoc2NvcmUpKSAlPiUKICBtdXRhdGUoZnJhYy5sYWJlbD10b3QubGFiZWwvdG90LmNlbGxzKSAlPiUKICAjIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JQogIGdncGxvdChhZXMoZnJhYy5sYWJlbCwgbWVhbi5zY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0yKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLnNjb3JlLXNkLnNjb3JlLCB5bWF4PW1lYW4uc2NvcmUrc2Quc2NvcmUpLCBhbHBoYT0wLjYpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAjIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNwYW49MS4yKSArCiAgZmFjZXRfZ3JpZCguIH4gbWV0aG9kKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICBzdGF0X2NvcihsYWJlbC54ID0gMC4yLCBsYWJlbC55PTAuMjUsIGNvbG9yPSJibGFjayIsIHNpemU9NSkgCiAgCiAgCmBgYAoKIyMjIEFncmVlbWVudCB3aXRoIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIG9mIEFUQUMgZGF0YQpDYWxjdWxhdGUgd2hpY2ggZnJhY3Rpb25zIG9mIE5OcyBpbiBiaW4gYmFzZWQgZ3JhcGggb2YgQVRBQyBjZWxscyBoYXZlIHRoZSBzYW1lIGFubm90YXRpb24KYGBge3J9CmsgPSA1MAphdGFjLnNldSA8LSBGaW5kTmVpZ2hib3JzKGF0YWMuc2V1LCBhc3NheSA9ICJBVEFDIiwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgZGltcyA9IDE6MTUsIGsucGFyYW0gPSBrKQoKYXRhYy5ubi5saXN0IDwtIGdldE5ObGlzdChhdGFjLnNldSkKCnNjb3JlLkNDQSA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhWy54LDFdID09IHByZWQuY2NhWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKc2NvcmUuQ29ub3MgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zWy54LDFdID09IHByZWQuY29ub3NbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQpzY29yZS5MaWdlciA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXJbLngsMV0gPT0gcHJlZC5saWdlclsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCgprbm5fc2NvcmVfZGYgPC0KICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLCBzY29yZS5MaWdlciwgc2NvcmUuQ0NBKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwKICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKQoKcXVhbnRzID0gc2VxKDAsMSwgYnkgPSAwLjA1KQpBVUVDREZfa25uX3Njb3JlIDwtIGtubl9zY29yZV9kZiAlPiUKICBzcGxpdCguJG1ldGhvZCkgJT4lCiAgbWFwX2RibCggfiAueCAlPiUKICAgICAgYXJyYW5nZShLTk5fc2NvcmUpICU+JSAKICAgICAge2VjZGYoLiRLTk5fc2NvcmUpKHF1YW50cyl9ICU+JSBBVUMocXVhbnRzLC4pCiAgICApCiAgCmtubl9zY29yZV9kZiAlPiUKICBtdXRhdGUoQVVDPUFVRUNERl9rbm5fc2NvcmVbbWV0aG9kXSkgJT4lCiAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCwgZmlsbD1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgZ2VvbV90ZXh0KGRhdGE9LiAlPiUgZ3JvdXBfYnkobWV0aG9kKSAlPiUgc3VtbWFyaXNlKEFVQz1tYXgoQVVDKSksIAogICAgICAgICAgICB4PTAuMDUsIGhqdXN0PTAsCiAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJBVUMgPSB7cm91bmQoQVVDLCAzKX0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgeWxhYigiRUNERiIpICsKICBnZ3NhdmUocGFzdGUob3V0ZGlyLCJLTk5fc2NvcmVfZWNkZl91bmlvbkhWRy5wbmciKSwgaGVpZ2h0ID0gNCwgd2lkdGg9NikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KCmZ1bGxfam9pbihwcmVkLmxhYmVscy5kZiwga25uX3Njb3JlX2RmKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIHN0YXRfZWNkZigpICsKICBmYWNldF93cmFwKCJwcmVkaWN0ZWQuaWQiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBjb29yZF9maXhlZCgpCmBgYApgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9NywgbWVzc2FnZT1GQUxTRX0KcGxvdF9LTk5lY2RmIDwtIGZ1bmN0aW9uKGNsdXN0ZXIpewogIGZ1bGxfam9pbihwcmVkLmxhYmVscy5kZiwga25uX3Njb3JlX2RmKSAlPiUKICAgIGZpbHRlcihwcmVkaWN0ZWQuaWQ9PWNsdXN0ZXIpICU+JQogICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICAgIHN0YXRfZWNkZihzaXplPTAuOCkgKwogICAgZmFjZXRfd3JhcCgicHJlZGljdGVkLmlkIikgKwogICAgeGxpbSgwLDEpICsgeWxpbSgwLDEpICsKICAgIGNvb3JkX2ZpeGVkKCkgKwogICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKfQoKRGltUGxvdENsdXN0ZXIgPC0gZnVuY3Rpb24oYW5ub3RhdGlvbl9jb2wsIGNsdXN0ZXIsIGxhYmVsKXsKICBoaWdobGlnaHQgPSB3aGljaChhdGFjLnNldUBtZXRhLmRhdGFbLGFubm90YXRpb25fY29sXT09Y2x1c3RlcikKICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIixjZWxscy5oaWdobGlnaHQgPSBoaWdobGlnaHQsIGNvbHMuaGlnaGxpZ2h0ID0gInJlZCIsIHB0LnNpemUgPSAwLjAyLCBzaXplcy5oaWdobGlnaHQgPSAwLjEpICsKICAgIGd1aWRlcyhjb2xvcj0ibm9uZSIpICsKICAgIGdndGl0bGUobGFiZWwgPSBsYWJlbCkKICB9CgpVTUFQc19jbHVzdGVyIDwtIGZ1bmN0aW9uKGNsdXN0ZXIpewogIGdnYXJyYW5nZShwbG90bGlzdD1pbWFwKGxpc3QoQ0NBPSJwcmVkaWN0ZWQuaWRfQ0NBIiwgQ29ub3M9InByZWRpY3RlZC5pZF9Db25vcyIsIExpZ2VyPSJwcmVkaWN0ZWQuaWRfTGlnZXIiKSwgfiBEaW1QbG90Q2x1c3RlcigueCwgY2x1c3RlciwgbGFiZWwgPSAueSApKSwgbmNvbD0zLCBucm93PTEpICU+JSBhbm5vdGF0ZV9maWd1cmUoY2x1c3RlcikKfQoKbWFwKGNlbGwudHlwZXMsIH4gZ2dhcnJhbmdlKHBsb3RfS05OZWNkZigueCksIFVNQVBzX2NsdXN0ZXIoLngpLCBucm93ID0gMiwgaGVpZ2h0cyA9IGMoMSwwLjgpKSkKCmBgYAoKCiMjIyMgV2hpY2ggY2VsbHMgYXJlIGluY29uc2lzdGVudGx5IGFsaWduZWQ/CmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9CnByZWQubGFiZWxzLmRmICU+JQogIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgY2VsbCkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2UoaXMubmEocHJlZGljdGVkLmlkKSwgIm5vbmUiLCBwcmVkaWN0ZWQuaWQpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCBzdHJhdHVtPXByZWRpY3RlZC5pZCwgYWxsdXZpdW09Y2VsbCwgZmlsbD1wcmVkaWN0ZWQuaWQsIGxhYmVsPXByZWRpY3RlZC5pZCkpICsKICBnZW9tX2Zsb3coKSArCiAgZ2VvbV9zdHJhdHVtKGNvbG9yPU5BKSArCiAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCmBgYAoKCgoKIyMjIyBXaGljaCBjZWxscyBhcmUgaW5jb25zaXN0ZW50bHkgc2NvcmVkPwpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9CmxpYnJhcnkoZ2dhbGx1dmlhbCkKcHJlZC5sYWJlbHMuZGYgJT4lCiAgc2VsZWN0KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBjZWxsKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShpcy5uYShwcmVkaWN0ZWQuaWQpLCAibm9uZSIsIHByZWRpY3RlZC5pZCkpICU+JQogIGdncGxvdChhZXMoeD1tZXRob2QsIHN0cmF0dW09cHJlZGljdGVkLmlkLCBhbGx1dml1bT1jZWxsLCBmaWxsPXByZWRpY3RlZC5pZCwgbGFiZWw9cHJlZGljdGVkLmlkKSkgKwogIGdlb21fZmxvdygpICsKICBnZW9tX3N0cmF0dW0oKSArCiAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCmBgYAoKIyMgQWNjZXNzaWJpbGl0eSBvZiBtYXJrZXJzClRha2luZyBtYXJrZXJzIGZyb20gRmlnLiBTMiBvZiBKUCdzIG1hbnVzY3JpcHQKYGBge3IsIGZpZy5oZWlnaHQ9MTMsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KdGh5bXVzLm1hcmtlcnMgPC0gYygiUFRQUkMiLCAiQ0QzRyIsICJUWVJPQlAiLCJDRDE5IiwiSE9YQTkiLCdGWFlEMicsIlNIM1RDMSIsIkNDUjkiLCJDRDhBIiwgIkNEOEIiLCJQRENEMSIsICJDUlRBTSIsIkNENDBMRyIsIkNDUjYiLCJGT1hQMyIsIlNPWDEzIiwiWk5GNjgzIiwiS0xSRDEiLCJUTkZTRjExIiwiVlBSRUIxIiwiTVM0QTEiLCAiQ0xFQzlBIiwgIkNMRUMxMEEiLCAiTEFNUDMiLCAiSUwzUkEiLCAiRkNHUjNCIiwgIkMyIiwiVFBTQjIiLAogICAgICAgICAgICAgICAgICAgICdJVEdBMkInLCJHWVBBIiwgIkNESDUiLCAiUkdTNSIsIkNESDEiLCAiUERHRlJBIiwiQ1JBQlAxIikKIyBwYm1jLm1hcmtlcnMgPC0gYygiQ0Q3OUEiLCAiTVM0QTEiLCAiQ0Q4QSIsICJDRDhCIiwgIkxZWiIpCiMgdGh5bXVzLm1hcmtlcnMgPC0gbGlzdChGYj1jKCJQREdGUkEiLCAiQ09MRUMxMSIsICJGQk4xIiwgIlBJMTYiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFZTTUM9YygiUERHRlJCIiwgJ0FDVEEyJywgIlJHUzUiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIEVuZG89YygiUEVDQU0xIiwgIkNESDUiLCJMWVZFMSIpLAojICAgICAgICAgICAgICAgICAgICAgICAgVEVDID0gYygiRVBDQU0iLCAiRk9YTjEiLCAiQ0NMMjUiLCAiQ0NMMTkiKQojICAgICAgICAgICAgICAgICAgICAgICAgKQp0aHltdXMubWFya2Vycy5kZiA8LSBpbWFwKHRoeW11cy5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgptYXJrZXIuYWNjZXNzLmRmIDwtIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh0aHltdXMubWFya2Vycywgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogICMgZnVsbF9qb2luKHRoeW11cy5tYXJrZXJzLmRmKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1sYWJlbF9jb2xzLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAicHJlZGljdGVkLmlkIikgJT4lCiAgZHBseXI6Om11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsIi4rXyIpKSAlPiUKICBmaWx0ZXIobWV0aG9kICVpbiUgYygiQ0NBIiwgIkxpZ2VyIiwgIkNvbm9zIikpIAoKb3JkZXJlZF9jZWxsX3R5cGVzIDwtIGMoIkROKFEpIiwgIkROKFApIiwgIkRQKFEpIiwgIkRQKFApIiwgIs6xzrJUKGVudHJ5KSIsICJDRDgrVCIsICJDRDQrVCIsICJDRDQrVG1lbSIsICdUcmVnJywgIs6zzrRUIiwgIk5LIiwgIklMQzMiLCAiREMxIiwgIkRDMiIsICJhREMiLCAicERDIiwgIk1hYyIsICJFcnkiLCAiRW5kbyIsICJWU01DIiwgIkZCXzEiLCAiRmJfMiIpCgptYXJrZXJzX3BsIDwtIAogIG1hcmtlci5hY2Nlc3MuZGYgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZCA9IGNhc2Vfd2hlbihzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNEOCIpIH4gIkNEOCtUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDQiKSB+ICJDRDQrVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gcHJlZGljdGVkLmlkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHMgPSBvcmRlcmVkX2NlbGxfdHlwZXMpKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCwgZ2VuZSkgJT4lCiAgZHBseXI6Om11dGF0ZShmcmFjLmNlbGxzPXN1bShsb2cuY291bnRzID4gMCkvbigpKSAlPiUKICAjIGZpbHRlcihtZXRob2Q9PSJDQ0EiKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KCBhZXMoIGdlbmUsIHByZWRpY3RlZC5pZCkpICsKICBnZW9tX3BvaW50KGFlcyhzaXplPWZyYWMuY2VsbHMsIGNvbG9yPWZyYWMuY2VsbHMpKSArCiAgZmFjZXRfZ3JpZChtZXRob2R+Liwgc3BhY2U9ImZyZWUiLCBzY2FsZXM9ImZyZWVfeCIpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChoaWdoPSJkYXJrYmx1ZSIsIGxvdz0id2hpdGUiKSArCiAgIyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUpKSAKCm1hcmtlcnNfcGwgCiAgCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiVGh5bXVzX21hcmtlcnNfYWNjZXNzaWJpbGl0eS5wbmciKSwgaGVpZ2h0ID0gMTYsIHdpZHRoID0gMTIpCmBgYAoKUmVwcm9kdWNpbmcgRmlnLjJIIG9uIFQtY2VsbCBkZXZlbG9wbWVudApgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEyfQp0LmNlbGwubWFya2VycyA8LSBsaXN0KGtub3duLm1hcmtlcnMgPSBjKCJDRDM0IiwgIklHTEwxIiwgIlRSR0MyIiwgIlRSREMiLCAiUFRDUkEiLCAiVFJCQzIiLCAiVFJBQyIsICJDRDQiLCAiQ0Q4QSIsICJDRDhCIiksCiAgICAgICAgICAgICAgICAgICAgICAgY2hlbW9raW5lLnJlY2VwdG9ycyA9IGMoIkNDUjkiLCAiQ0NSNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHRjci5hY3RpdmF0aW9uID0gYygiQ0Q1IiwgIkNEMjciKSwKICAgICAgICAgICAgICAgICAgICAgICBwcm9saWZlcmF0aW9uPWMoIlBDTkEiLCAiQ0RLMSIsICJNS0k2NyIpLAogICAgICAgICAgICAgICAgICAgICAgIGN5Y2xpbi5EID0gYygiQ0NORDIiLCAiQ0NORDMiKSwKICAgICAgICAgICAgICAgICAgICAgICByZWNvbWJpbmF0aW9uPWMoIlJBRzEiLCAiUkFHMiIpLAogICAgICAgICAgICAgICAgICAgICAgIGFwb3B0b3Npcz1jKCJIUksiLCJCTUYiLCAiVFA1M0lOUDEiKSwKICAgICAgICAgICAgICAgICAgICAgICBzdGFnZS5tYXJrZXJzID0gYygiU1QxOCIsICJISVZFUDMiLCAiUkdQRDMiLCAiU01QRDMiLCAiQVFQMyIsICJST1JDIiwgIlNBVEIxIiwgIlRPWDIiKQogICAgICAgICAgICAgICAgICAgICAgICkgCnQuY2VsbC5tYXJrZXJzLmRmIDwtIGltYXAodC5jZWxsLm1hcmtlcnMsIH4gZGF0YS5mcmFtZShnZW5lPS54LCBjZWxsLnR5cGUuY2xhc3M9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykKCm9yZGVyZWQudGNlbGxzIDwtIGMoIkROKFApIiwgIkROKFEpIiwgIkRQKFApIiwgIkRQKFEpIiwizrHOslQoZW50cnkpIiwgIkNEOCtUIiwgIkNENCtUIikKCnRjZWxscy5tYXJrZXJzLmRmIDwtIAogIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh1bmxpc3QodC5jZWxsLm1hcmtlcnMpLCByb3duYW1lcyhhdGFjLnNldUBhc3NheXMkQUNUSVZJVFkpKSxdICU+JQogIGFzLm1hdHJpeCgpICU+JQogIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpLCB2YWx1ZS5uYW1lPSJsb2cuY291bnRzIikgJT4lCiAgZnVsbF9qb2luKHJvd25hbWVzX3RvX2NvbHVtbihhdGFjLnNldUBtZXRhLmRhdGFbLCBsYWJlbF9jb2xzXSwgImNlbGwiKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9bGFiZWxfY29scywgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gInByZWRpY3RlZC5pZCIpICU+JQogIGRwbHlyOjptdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCIuK18iKSkgJT4lCiAgZmlsdGVyKG1ldGhvZCAlaW4lIGMoIkNDQSIsICJMaWdlciIsICJDb25vcyIpKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNEOCsiKSwgIkNEOCtUIiwgcHJlZGljdGVkLmlkKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDQrIiksICJDRDQrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIGZpbHRlcihwcmVkaWN0ZWQuaWQgJWluJSBvcmRlcmVkLnRjZWxscykgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGdlbmUpICU+JQogIGRwbHlyOjptdXRhdGUoZnJhYy5jZWxscz1zdW0obG9nLmNvdW50cyA+IDApL24oKSwgbWVhbi5hY2M9bWVhbihsb2cuY291bnRzKSkgJT4lCiAgdW5ncm91cCgpIAoKdGNlbGxzLm1hcmtlcnMuZGYgJT4lCiAgZnVsbF9qb2luKHQuY2VsbC5tYXJrZXJzLmRmKSAlPiUKICAjIGZpbHRlcihtZXRob2Q9PSJDQ0EiKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscz1vcmRlcmVkLnRjZWxscykpICU+JQogIGdncGxvdChhZXMoIHByZWRpY3RlZC5pZCwgZ2VuZSkpICsKICBmYWNldF9ncmlkKGNlbGwudHlwZS5jbGFzc35tZXRob2QsIHNjYWxlcyA9ICJmcmVlX3kiLCBzcGFjZT0iZnJlZSIpICsKICBnZW9tX3BvaW50KGFlcyhzaXplPWZyYWMuY2VsbHMsIGNvbG9yPWZyYWMuY2VsbHMpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoaGlnaD0iZGFya2JsdWUiLCBsb3c9IndoaXRlIikgKwogICMgc2NhbGVfY29sb3JfZ3JhZGllbnQyKG1pZHBvaW50ID0gMC41KSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSwKICAgICAgICBzdHJpcC50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGU9MCkpIAoKZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ0Y2VsbF9tYXJrZXJzLnBuZyIpLCBoZWlnaHQgPSAxNCwgd2lkdGggPSAxNCkKCmBgYAoKIyMjIENvbXBhcmUgZmVhdHVyZSBzZWxlY3Rpb24gc3RyYXRlZ3kgKHJlZmVyZW5jZSBiYXNlZCkKYGBge3J9CnNldS5jY2EucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDQ0FfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKQpzZXUubGlnZXIucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJMaWdlcl9yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpCnNldS5jb25vcy5yZWYgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikKCmludGVncmF0ZV9mZWF0dXJlc19yZWYgPC0gc2Nhbigifi9tb2RlbHMvaW50RmVhdHVyZXNfcmVmZXJlbmNlX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLnR4dCIsIHdoYXQgPSAiIikKCmludC5saXN0LnJlZiA8LSBsaXN0KENDQT1zZXUuY2NhLnJlZiwgTGlnZXI9c2V1LmxpZ2VyLnJlZiwgQ29ub3M9c2V1LmNvbm9zLnJlZikKCiMjIEFkZCB0byBhdGFjIFNldXJhdCBvYmplY3QKcHJlZC5jY2EucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnJlZiwgIkNDQV9yZWYiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmxpZ2VyLnJlZiwgIkxpZ2VyX3JlZiIpCnByZWQuY29ub3MucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MucmVmLCAiQ29ub3NfcmVmIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS5yZWYsIHByZWQubGlnZXIucmVmLCBwcmVkLmNvbm9zLnJlZikpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xOSwgZmlnLmhlaWdodD05fQpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwbG90bGlzdCA9IGxpc3QoCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQV9yZWYiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXJfcmVmIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpCiAgKSwKICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD0zLCBucm93PTEKKSAKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE2fQpwcmVkLmxhYmVscy5yZWYuZGYgPC0gaW1hcChsaXN0KENDQT1wcmVkLmNjYS5yZWYsIExpZ2VyPXByZWQubGlnZXIucmVmLCBDb25vcz1wcmVkLmNvbm9zLnJlZiksIH4gCiAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigueCwgImNlbGwiKSAlPiUKICAgICAgcmVuYW1lX2FsbChmdW5zKHN0cl9yZW1vdmUoLiwgc3RyX2MoIl8iLC55KSkpKSAlPiUKICAgICAgbXV0YXRlKG1ldGhvZD0ueSkKICAgICkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JQogIG11dGF0ZShzY29yZT1pZmVsc2UoaXMubmEoc2NvcmVfcmVmKSwgMCwgc2NvcmVfcmVmKSkKCmZ1bGxfam9pbigKICBwcmVkLmxhYmVscy5kZiwKICBzZWxlY3QocHJlZC5sYWJlbHMucmVmLmRmLCBjZWxsLCBwcmVkaWN0ZWQuaWRfcmVmLCBzY29yZV9yZWYsIG1ldGhvZCksCiAgYnk9YygiY2VsbCIsICJtZXRob2QiKQogICkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JQogIG11dGF0ZShuX3ByZWQ9bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpICU+JQogIHN1bW1hcmlzZShuPW4oKSwgbl9wcmVkPW1heChuX3ByZWQpKSAlPiUKICBtdXRhdGUoZnJhYz1uL25fcHJlZCkgJT4lCiAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPWZyYWMpKSArCiAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTMpICsKICBjb29yZF9maXhlZCgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArCiAgeWxhYigiRmVhdC4gc2VsZWN0aW9uOiByZWZlcmVuY2UgSFZHIikgKyB4bGFiKCJGZWF0LiBzZWxlY3Rpb246IHVuaW9uIEhWRyIpICsKICB0aGVtZV9jb3dwbG90KGZvbnRfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlLnBuZyIpLCBoZWlnaHQgPSAxMiwgd2lkdGg9MTApCmBgYApgYGB7cn0KCnNjb3JlLkNDQS5yZWYgPC0gICBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhLnJlZlsueCwxXSA9PSBwcmVkLmNjYS5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQpzY29yZS5Db25vcy5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zLnJlZlsueCwxXSA9PSBwcmVkLmNvbm9zLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCnNjb3JlLkxpZ2VyLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXIucmVmWy54LDFdID09IHByZWQubGlnZXIucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKCmtubl9zY29yZV9yZWZfZGYgPC0KICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLnJlZiwgc2NvcmUuTGlnZXIucmVmLCBzY29yZS5DQ0EucmVmKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwKICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKQoKcXVhbnRzID0gc2VxKDAsMSwgYnkgPSAwLjA1KQpBVUVDREZfa25uX3Njb3JlIDwtIGtubl9zY29yZV9yZWZfZGYgJT4lCiAgc3BsaXQoLiRtZXRob2QpICU+JQogIG1hcF9kYmwoIH4gLnggJT4lCiAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgCiAgICAgIHtlY2RmKC4kS05OX3Njb3JlKShxdWFudHMpfSAlPiUgQVVDKHF1YW50cywuKQogICAgKQogIAprbm5fc2NvcmVfcmVmX2RmICU+JQogIG11dGF0ZShBVUM9QVVFQ0RGX2tubl9zY29yZVttZXRob2RdKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kLCBmaWxsPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoc2l6ZT0xKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBnZW9tX3RleHQoZGF0YT0uICU+JSBncm91cF9ieShtZXRob2QpICU+JSBzdW1tYXJpc2UoQVVDPW1heChBVUMpKSwgCiAgICAgICAgICAgIHg9MC4wNSwgaGp1c3Q9MCwKICAgICAgICAgICAgYWVzKGxhYmVsPWdsdWUoIkFVQyA9IHtyb3VuZChBVUMsIDMpfSIpLCB5PWMoMC45MCwgMC45NSwgMSkpKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB5bGFiKCJFQ0RGIikgCmBgYAoKIyMjIElzIHRoZSB1bmlvbiBvciB0aGUgcmVmZXJlbmNlIGJlc3QgbWFpbnRhaW5pbmcgdGhlIHN0cnVjdHVyZSBvZiB0aGUgQVRBQz8KYGBge3IsIGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTd9CmsgPSA1MAphdGFjLnNldSA8LSBGaW5kTmVpZ2hib3JzKGF0YWMuc2V1LCBhc3NheSA9ICJBVEFDIiwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgZGltcyA9IDE6MTUsIGsucGFyYW0gPSBrKQoKYXRhYy5ubi5saXN0IDwtIGdldE5ObGlzdChhdGFjLnNldSkKCnNjb3JlLkNDQSA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhWy54LDFdID09IHByZWQuY2NhWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKc2NvcmUuQ29ub3MgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zWy54LDFdID09IHByZWQuY29ub3NbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQpzY29yZS5MaWdlciA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXJbLngsMV0gPT0gcHJlZC5saWdlclsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCgprbm5fc2NvcmVfZGYgPC0KICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLCBzY29yZS5MaWdlciwgc2NvcmUuQ0NBKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwKICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKQoKCnNjb3JlLkNDQS5yZWYgPC0gICBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhLnJlZlsueCwxXSA9PSBwcmVkLmNjYS5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQpzY29yZS5Db25vcy5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zLnJlZlsueCwxXSA9PSBwcmVkLmNvbm9zLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCnNjb3JlLkxpZ2VyLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXIucmVmWy54LDFdID09IHByZWQubGlnZXIucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKCmtubl9zY29yZV9yZWZfZGYgPC0KICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLnJlZiwgc2NvcmUuTGlnZXIucmVmLCBzY29yZS5DQ0EucmVmKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwKICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKQoKCmJpbmRfcm93cyhrbm5fc2NvcmVfZGYsIGtubl9zY29yZV9yZWZfZGYpICU+JQogIG11dGF0ZShmZWF0dXJlLnNlbGVjdGlvbj1pZmVsc2Uoc3RyX2RldGVjdChtZXRob2QsICJyZWYiKSwgInJlZiIsICJ1bmlvbiIpKSAlPiUKICBtdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAiLnJlZiIpKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9ZmVhdHVyZS5zZWxlY3Rpb24sIGZpbGw9bWV0aG9kKSkgKwogIHN0YXRfZWNkZihzaXplPTEpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGZhY2V0X3dyYXAobWV0aG9kfi4pICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHlsYWIoIkVDREYiKSArCiAgZ2d0aXRsZShwYXN0ZSgiSyA9IiwgaykpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInVuaW9uVlNyZWZlcmVuY2VfS05OLnBuZyIpLCBoZWlnaHQgPSA0LCB3aWR0aCA9IDEwKQoKYGBgCgotLS0KCmBgYHtyfQpwbG90bHk6OmdncGxvdGx5KERpbVBsb3Qob3JpZy5STkEuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIpKQpwbG90bHk6OmdncGxvdGx5KERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIikpCmBgYAoKCiMjIyBUaG91Z2h0cwotIENvbm9zIHNjb3JlcyBhIGxvdCBvZiBjZWxscyB3aXRoIGhpZ2ggY29uZmlkZW5jZSwgYnV0IGZhaWxzIHRvIGFzc2lnbiBjZWxscyB0byBkaWZmaWN1bHQgY2x1c3RlcnMgCi0gQ0NBIHJlc2VtYmxlcyB0aGUgY29tcG9zaXRpb24gb2YgdGhlIFJOQSBkYXRhIGJldHRlciwgYnV0IGN1cmlvdXMgdGhhdCB0aGUgb3RoZXIgbWV0aG9kcyBpZGVudGlmeSB3YXkgbW9yZSAKCgoKCgoKCgoKCg==